package calao;

import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.Locale;
import java.util.Properties;

import javax.servlet.ServletException;

import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import static javax.servlet.http.HttpServletResponse.*;

import calao.client.NotConnectedException;
import calao.csp.ScriptCommand;
import calao.util.Logger;
import static calao.util.Logger.*;

public class DesktopServlet 
extends WorkspaceServlet 
{

	static class ServiceRequest {
		final Service service;
		final Exception exception;
		ServiceRequest(Service service) {
			this.service = service;
			this.exception = null;
		}
		ServiceRequest(Exception e) {
			this.service = null;
			this.exception = e;
		}
	}
	
	public static final String ROOT_PATH = "/";
	public static final String LOGIN_PATH = "/.login";
	public static final String DESKTOP_PATH = "/.desktop";
	public static final String SERVICE_PATH = "/.svc/";

	static final ThreadLocal<ServiceRequest> serviceRequest = 
		new ThreadLocal<ServiceRequest>();
	
	String defaultDesktop;

	final RemoteServiceServlet serviceServlet = new RemoteServiceServlet()
	{
		@Override 
		public String processCall(String payload) 
		throws SerializationException {
			try {
				ServiceRequest request = serviceRequest.get();
				if (request.exception!=null) {
					return RPC.encodeResponseForFailure(null, request.exception);
				}
				RPCRequest rpcRequest = RPC.decodeRequest(payload, 
						request.service.getClass());
				Logger.log(DEBUG, "Processing remote call to %s", 
						rpcRequest.getMethod().getName());
				return RPC.invokeAndEncodeResponse(request.service, 
						rpcRequest.getMethod(),
						rpcRequest.getParameters());
			} 
			catch (IncompatibleRemoteServiceException ex) {
				calao.util.Logger.log(ex);
				return RPC.encodeResponseForFailure(null, ex);
			}			
		}
	};
	
	@Override
	public void init()
	throws ServletException
	{
		try {
			serviceServlet.init(getServletConfig());
			if (defaultDesktop==null) {
				defaultDesktop = getServer().config.defaultDesktop;
			}
		}
		catch (Exception e) {
			Logger.log(e);
			throw new ServletException(e);
		}
	}

	public void process(WebRequest request)
	throws ServletException, IOException
	{
		String rpath = request.resourcePath;
		// TODO use workspace-specific desktop
		String desktopName = defaultDesktop;
		File desktop = getDesktop(desktopName);
		// case desktop not found
		if (desktop==null) {
			request.respondWithError(SC_NOT_FOUND, "Cannot find desktop: "+desktopName);
		}
		// case of login page
		else if (rpath.equals(LOGIN_PATH)) {
			String alias = request.getParameter(LoginPage.ALIAS_PARAM);
			String password = request.getParameter(LoginPage.PASSWORD_PARAM);
			String locale =  request.getParameter(LoginPage.LOCALE_PARAM);
			final LoginPage page = HtmlPage.getPage(LoginPage.class, 
					locale!=null? new Locale(locale) : request.getLocale());
			if (alias!=null && password!=null) {
				if (request.openSession(alias, password)!=null) {
					request.redirectTo(ROOT_PATH);
					return;
				}
				page.setMessage(page.loginFailed());
			}
			URL url = getDesktopResource(desktop, "login.csp");
			if (url==null) request.respondWith(page);
			else {
				request.getScriptInterpreter().register("login", new ScriptCommand() {
					public void execute(String[] argv, OutputStream output) 
					throws IOException {
						page.setOutput(output);
						page.writeLoginPanel();
						page.flushOutput();
					}
				});
				request.respondWithContentFrom(url);
			}
		}
		// case of desktop page
		else if (rpath.equals(ROOT_PATH) || rpath.equals(DESKTOP_PATH)) {
			Session session = request.getSession();
			if (session==null) {
				request.redirectTo(LOGIN_PATH);
				return;
			}
			DesktopPage page = new DesktopPage(desktopName);
			String lang = session.getProperties().getProperty(calao.Properties.LANGUAGE);
			page.setLocale(lang!=null? new Locale(lang) : request.getLocale());
			page.setDesktopProperties(getDesktopProperties(desktop));
			request.respondWith(page);		
		}
		// case of desktop service
		else if (rpath.startsWith(SERVICE_PATH)) {
			if (request.getSession()==null) {
				Logger.log(DEBUG, "Request %d for service outside of session",
						request.id);
				handle(request, new ServiceRequest(new NotConnectedException()));
				return;
			}
			String svcName = rpath.substring(SERVICE_PATH.length());
			Service svc = request.getSession().getWorkspace().getService(svcName);
			if (svc==null) {
				request.respondWithError(SC_NOT_FOUND, "Invalid service "+svcName);
			}
			else {
				if (svc instanceof WebRequestHandler) {
					((WebRequestHandler)svc).handle(request);
				}
				else handle(request, new ServiceRequest(svc));
			}
		}
		// case of GWT resource
		else {
			URL url = getDesktopResource(desktop, rpath);
			if (url==null) {
				request.respondWithError(SC_NOT_FOUND, "Cannot find resource "+rpath);
			}
			else request.respondWithContentFrom(url);
		}		
	}
	
	protected File getDesktop(String desktopName) 
	{
		File file = new File(getServer().config.desktopsDir, desktopName);
		if (file.isDirectory()) return file;
		else return null;
	}
	
	protected URL getDesktopResource(File desktop, String path)
	{
		try {
			File file = new File(desktop, path);
			return file.canRead()? file.toURI().toURL() : null;
		}
		catch (Exception e) {
			Logger.log(e);
			return null;
		}
	}
		
	public void setDefaultDesktop(String desktopName)
	{
		this.defaultDesktop = desktopName;
	}
	
	private void handle(WebRequest webReq, ServiceRequest svcReq)
	throws IOException, ServletException
	{
		try {
			serviceRequest.set(svcReq);
			webReq.handleWith(serviceServlet);
		}
		finally {
			serviceRequest.set(null);
		}
	}
	
	protected Properties getDesktopProperties(File desktop) 
	throws IOException
	{
		Properties dprops = new Properties();
		URL url = getDesktopResource(desktop, "desktop.properties");
		if (url!=null) {
			InputStream stream = url.openStream();
			dprops.load(stream);
			stream.close();
		}
		return dprops;
	}

}
